package com.jeeplus.modules.api.utils;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;

import javax.net.ssl.SSLContext;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSONObject;
import com.jeeplus.common.config.Global;
import com.jeeplus.common.utils.IdGen;
import com.jeeplus.common.utils.StringUtils;

public class WxPayUtils {
	private static Logger logger = LoggerFactory.getLogger(WxPayUtils.class);
	/**
	 * 应用 id
	 */
	private static final String APP_ID = "wx2a2e261b75f0c23d";
	private static final String APP_ID_JSAPI = "wxfd00b75fa13f1b76";
	private static final String WXAPP_ID_JSAPI = "wx52cd2569236ed6cd";
	private static final String APP_ID_QYFK = "wxbe05b7817f449014";

	/**
	 * 商户号 mch id
	 */
	private static final String MCH_ID = "1482951932";

	/**
	 * 密钥(key)
	 */
	private static final String KEY = "ao32oinasodf3240x0v432jf32f2322s";

	/**
	 * 异步回调通知地址
	 */
	private static final String NOTIFY_URL = Global.getConfig("filePath") + "/webhook/wxPayNotify";

	/**
	 * 交易类型
	 */
	public static final String TRADE_TYPE_APP = "APP"; // app支付
	public static final String TRADE_TYPE_JSAPI = "JSAPI";// JSAPI支付
	public static final String TRADE_TYPE_WXAPPJSAPI = "JSWXPPAPI";// 小程序支付
	public static final String TRADE_TYPE_MWEB = "MWEB";// H5支付
	public static final String TRADE_TYPE_NATIVE = "NATIVE";// Native支付
	/**
	 * 微信接口返回结果成功状态值(SUCCESS)
	 */
	private static final String SUCCESS = "SUCCESS";

	/**
	 * 微信接口返回结果失败状态值(FAIL)
	 */
	private static final String FAIL = "FAIL";

	/**
	 * 微信签名字段名(sign)
	 */
	private static final String FIELD_SIGN = "sign";

	/**
	 * 微信「统一下单」接口地址
	 */
	private static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

	/**
	 * 微信订单查询接口
	 */
	private static final String ORDER_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
	/**
	 * 微信企业付款地址
	 */
	private static final String TRANSFERS_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
	/**
	 * 证书路径
	 */
//	private static final String CERTNAME = "E:/cert/apiclient_cert.p12";//个人测试地址
	private static final String CERTNAME = "/cert/apiclient_cert.p12";//正式地址
	/**
	 * 退款地址
	 */
	private static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";

	/**
	 * 创建微信预支付订单
	 * 
	 * @param trade_type   交易类型
	 * @param openid   用户标识(trade_type=JSAPI时（即JSAPI支付），此参数必传)
	 * @param body   商品描述
	 * @param out_trade_no   订单号
	 * @param price    价格 /元
	 * @param ip IP地址
	 * @return
	 * @throws Exception
	 */
	public static Map<String, String> createOrder(String trade_type, String openid, String body, String out_trade_no, String price, String ip) throws Exception {
		String uuid = getUUID();
		/**
		 * 生成微信「统一下单」请求数据
		 */
		Map<String, String> dataMap = new HashMap<>();
		if(trade_type.equals("JSAPI")){
			dataMap.put("appid", APP_ID_JSAPI);//公众号
		}else if(trade_type.equals("APP")){
			dataMap.put("appid", APP_ID);//app
		}else if(trade_type.equals("JSWXPPAPI")){
			dataMap.put("appid", WXAPP_ID_JSAPI);//小程序
		}else if(trade_type.equals("MWEB")){
			dataMap.put("appid", APP_ID_QYFK);//H5
		}
		dataMap.put("mch_id", MCH_ID);
		dataMap.put("nonce_str", uuid);
		dataMap.put("body", body);
		dataMap.put("out_trade_no", out_trade_no);
		dataMap.put("total_fee", new BigDecimal(price).multiply(new BigDecimal("100")).intValue() + "");
		dataMap.put("spbill_create_ip", ip);
		dataMap.put("notify_url", NOTIFY_URL);
		if(trade_type.equals("JSWXPPAPI")){
			dataMap.put("trade_type","JSAPI");//小程序
		}else{
			dataMap.put("trade_type",trade_type);
		}
		if (TRADE_TYPE_JSAPI.equals(trade_type)||TRADE_TYPE_WXAPPJSAPI.equals(trade_type)) {
			dataMap.put("openid", openid);
		}
		/**
		 * 生成签名(MD5 编码)
		 */
		String md5Sign = getMD5Sign(dataMap, KEY, FIELD_SIGN);
		dataMap.put("sign", md5Sign);

		logger.debug("微信支付请求参数：" + dataMap.toString());
		/**
		 * 发送请求数据
		 */
		String respXml = requestWithoutCert(UNIFIEDORDER_URL, dataMap, 5000, 10000);
		/**
		 * 解析微信返回数据
		 */
		dataMap.clear();
		dataMap = processResponseXml(respXml);
		//dataMap.put("noncestr", uuid);
		logger.debug("微信支付返回参数：" + dataMap.toString());
		/**
		 * 没有生成预支付订单,直接返回
		 */
		if (dataMap.get("prepay_id") == null) {
			throw new RuntimeException(dataMap.get("return_msg") + ";" + dataMap.get("err_code_des"));
		}
		/**
		 * 生成微信APP支付「调起支付接口」的请求参数
		 */
		Map<String, String> resultMap;

		if(trade_type.equals("JSAPI")||trade_type.equals("JSWXPPAPI")){
			resultMap = getJsapiPayOrder(dataMap, KEY, FIELD_SIGN);
		}else{
			resultMap = getPayOrder(dataMap, KEY, FIELD_SIGN);
		}
		/**
		 * 添加预支付订单创建成功标识
		 */
		resultMap.put("pre_pay_order_status", "success");

		/**
		 * 返回处理结果
		 */
		return resultMap;
	}
	

	/** 微信退款
     * @param out_trade_no 商户这边的订单号 
     * @param transaction_id 微信那边的交易单号 
     * @param totalFee 订单的金额 单位为分
     * @param refundFee 要退的金额 单位为分
     */  
    public static JSONObject refund(String out_trade_no, String transaction_id, String totalFee, String refundFee, String trade_type) {
    	try{
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            FileInputStream instream = new FileInputStream(CERTNAME);
            try {
                keyStore.load(instream, MCH_ID.toCharArray());
            }finally {
                instream.close();
            }
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, MCH_ID.toCharArray()).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    sslcontext, new String[] { "TLSv1" }, null,
                    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
            HttpPost httppost = new HttpPost(REFUND_URL);
            
            String nonceStr = getUUID();
			System.out.println("微信支付退款类型"+trade_type);
			SortedMap<String,String> parameters = new TreeMap<String,String>();
			parameters.put("appid", APP_ID);
            parameters.put("mch_id", MCH_ID);
            parameters.put("nonce_str", nonceStr);
            parameters.put("out_trade_no", out_trade_no);
            if (StringUtils.isNotBlank(transaction_id)) {
            	parameters.put("transaction_id", transaction_id);
            }
            parameters.put("out_refund_no", nonceStr);
            parameters.put("fee_type", "CNY");
            parameters.put("total_fee", totalFee);
            parameters.put("refund_fee", refundFee);
            parameters.put("op_user_id", MCH_ID);
            String sign = getMD5Sign(parameters, KEY, FIELD_SIGN);
            parameters.put("sign", sign);
            String xml = map2Xml(parameters);
			System.out.println("微信支付xml"+xml);
            logger.debug("微信退款请求XML：" + xml);
            try {  
                StringEntity se = new StringEntity(xml);  
                httppost.setEntity(se);  
                CloseableHttpResponse responseEntry = httpclient.execute(httppost);  
                try {  
                    HttpEntity entity = responseEntry.getEntity();
                    if (entity != null) {
                        SAXReader saxReader = new SAXReader();
                        Document document = saxReader.read(entity.getContent());  
                        Element rootElt = document.getRootElement();  
                        logger.debug(rootElt.asXML());
                        String resultCode = rootElt.elementText("result_code");  
                        JSONObject result = new JSONObject();  
                        if("SUCCESS".equals(resultCode)){  
                            result.put("weixinPayUrl", rootElt.elementText("code_url"));  
                            result.put("prepayId", rootElt.elementText("prepay_id"));  
                            result.put("status","success");  
                            result.put("msg","success");  
                        }else{  
                            result.put("status","false");  
                            result.put("msg",rootElt.elementText("err_code_des"));  
                        }
                        logger.debug("微信退款result:" + result.toString());
                        return result;  
                    }  
                    EntityUtils.consume(entity);  
                }  
                finally {  
                    responseEntry.close();  
                }  
            }  
            finally {  
                httpclient.close();  
            }  
            return null;  
        }catch(Exception e){  
            e.printStackTrace(); 
            logger.error("微信退款失败：" + e.getMessage());
            JSONObject result = new JSONObject();  
            result.put("status","error");  
            result.put("msg",e.getMessage());  
            return result;  
        }  
    }
	/**
	 * 微信企业付款
	 *
	 * @param openid
	 * @param payMoney
	 * @param orderNum
	 * @return
	 * @throws Exception
	 */
	public static LinkedHashMap<String, String> wxqyPay( String openid, String payMoney, String orderNum, String desc) throws Exception {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		LinkedHashMap<String, String> resultObj = new LinkedHashMap<String, String>();
		try {
			KeyStore keyStore = KeyStore.getInstance("PKCS12");
			FileInputStream instream = new FileInputStream(CERTNAME);
			try {
				keyStore.load(instream, MCH_ID.toCharArray());
			}finally {
				instream.close();
			}
			// Trust own CA and all self-signed certs
			SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, MCH_ID.toCharArray()).build();
			//指定TLS版本, Allow TLSv1 protocol only
			SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
					sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
			//设置httpclient的SSLSocketFactory
			CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
			HttpPost httppost = new HttpPost(TRANSFERS_URL);

			// 生成的随机字符串
			String nonce_str = IdGen.uuid();

			Map<String, String> packageParams = new HashMap<String, String>();
			packageParams.put("mch_appid", APP_ID_JSAPI);
			packageParams.put("mchid", MCH_ID);
			packageParams.put("nonce_str", nonce_str);
			packageParams.put("partner_trade_no", orderNum);// 商户订单号
			packageParams.put("openid", openid);//  	商户appid下，某用户的openid
			packageParams.put("check_name", "NO_CHECK");// NO_CHECK：不校验真实姓名  FORCE_CHECK：强校验真实姓名
			packageParams.put("amount", payMoney);// 支付金额，企业付款金额，单位为分
			packageParams.put("desc", desc);
			String prestr = createLinkString(packageParams); // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
			// MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
			String mysign = sign(prestr, KEY, "utf-8").toUpperCase();
			// 拼接统一下单接口使用的xml数据，要将上一步生成的签名一起拼接进去
			String xml = "<xml>" + "<mch_appid>" + APP_ID_JSAPI + "</mch_appid>" + "<mchid>" + MCH_ID + "</mchid>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<partner_trade_no>"
					+ orderNum + "</partner_trade_no>" + "<openid>" + openid + "</openid>"+ "<check_name>" + "NO_CHECK" + "</check_name>"
					+ "<amount>" + payMoney + "</amount>"+ "<desc>" + desc + "</desc>"
					+ "<sign>" + mysign + "</sign>" + "</xml>";
			logger.debug("调试模式_企业付款接口 请求XML数据：" + xml);

			httppost.addHeader("Content-Type", "text/xml");
			StringEntity se = new StringEntity(xml,"UTF-8");
			httppost.setEntity(se);
			CloseableHttpResponse responseEntry = httpclient.execute(httppost);
			HttpEntity entity = responseEntry.getEntity();
			if (entity != null) {
				System.out.println("响应内容长度 : "+ entity.getContentLength());
				SAXReader saxReader = new SAXReader();
				Document document = saxReader.read(entity.getContent());
				Element rootElt = document.getRootElement();
				String resultCode = rootElt.elementText("result_code");// 返回状态码
				String return_msg = rootElt.elementText("return_msg");// 返回的预付单信息
				resultObj.put("return_code", resultCode);
				resultObj.put("return_msg", return_msg);
				if(resultCode.equals("SUCCESS")){
					resultObj.put("result_code", rootElt.elementText("result_code"));
					resultObj.put("payment_no", rootElt.elementText("payment_no"));
					resultObj.put("partner_trade_no", rootElt.elementText("partner_trade_no"));
					resultObj.put("payment_time", rootElt.elementText("payment_time"));
					resultObj.put("msg","success");
					resultObj.put("status","0");
				}else{
					resultObj.put("status","1");
					resultObj.put("msg", rootElt.elementText("err_code_des"));
					resultObj.put("return_msg", return_msg);
				}
			}
			EntityUtils.consume(entity);

		} catch (Exception e) {
			logger.error("系统异常:" + e.getMessage());
		}
		return resultObj;
	}
	/**
	 * 微信支付
	 * 
	 * @param openid
	 * @param payMoney
	 * @param body
	 * @param attach
	 * @param orderNum
	 * @return
	 * @throws Exception
	 */
	public static LinkedHashMap<String, String> wxPay(String openid, String payMoney, String body, String attach, String orderNum) throws Exception {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		LinkedHashMap<String, String> resultObj = new LinkedHashMap<String, String>();
		try {
			// 生成的随机字符串
			String nonce_str = IdGen.uuid();
			// 获取客户端的ip地址
			String spbill_create_ip = StringUtils.getRemoteAddr(request);
			Map<String, String> packageParams = new HashMap<String, String>();
			packageParams.put("appid", APP_ID);
			packageParams.put("mch_id", MCH_ID);
			packageParams.put("nonce_str", nonce_str);
			packageParams.put("body", body);
			packageParams.put("out_trade_no", orderNum);// 商户订单号
			packageParams.put("total_fee", payMoney);// 支付金额，这边需要转成字符串类型，否则后面的签名会失败
			packageParams.put("spbill_create_ip", spbill_create_ip);
			packageParams.put("notify_url", NOTIFY_URL);// 支付成功后的回调地址
			packageParams.put("trade_type", TRADE_TYPE_JSAPI);// 支付方式
			if (StringUtils.isNotBlank(openid)) {
				packageParams.put("openid", openid);
			}
			packageParams.put("attach", attach); // 附加数据，在查询API和支付通知中原样返回，可作为自定义参数使用。
			String prestr = createLinkString(packageParams); // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
			// MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
			String mysign = sign(prestr, KEY, "utf-8").toUpperCase();
			// 拼接统一下单接口使用的xml数据，要将上一步生成的签名一起拼接进去
			String xml = "<xml>" + "<appid>" + APP_ID + "</appid>" + "<body><![CDATA[" + body + "]]></body>"
					+ "<mch_id>" + MCH_ID + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<notify_url>"
					+ NOTIFY_URL + "</notify_url>"
					+ (StringUtils.isNotBlank(openid) ? "<openid>" + openid + "</openid>" : "") + "<out_trade_no>"
					+ orderNum + "</out_trade_no>" + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"
					+ "<total_fee>" + payMoney + "</total_fee>" + "<trade_type>" + TRADE_TYPE_JSAPI + "</trade_type>"
					+ "<sign>" + mysign + "</sign>" + "<attach>" + attach + "</attach>" + "</xml>";
			logger.debug("调试模式_统一下单接口 请求XML数据：" + xml);
			// 调用统一下单接口，并接受返回的结果
			String result = httpRequest(UNIFIEDORDER_URL, "POST", xml);
			// System.out.println("调试模式_统一下单接口 返回XML数据：" + result);
			logger.debug("调试模式_统一下单接口 返回XML数据：" + result);
			// 将解析结果存储在HashMap中
			Map<String, String> map = doXMLParse(result);
			String return_code = map.get("return_code");// 返回状态码
			if ("SUCCESS".equals(return_code)) {
				String prepay_id = map.get("prepay_id");// 返回的预付单信息
				resultObj.put("noncestr", nonce_str);
				resultObj.put("prepayid", prepay_id);
				Long timeStamp = System.currentTimeMillis() / 1000;
				resultObj.put("timestamp", timeStamp + "");// 这边要将返回的时间戳转化成字符串，不然小程序端调用wx.requestPayment方法会报签名错误
				// 拼接签名需要的参数
				String stringSignTemp = "appId=" + APP_ID + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id
						+ "&signType=MD5&timeStamp=" + timeStamp;
				// 再次签名，这个签名用于小程序端调用wx.requesetPayment方法
				String paySign = sign(stringSignTemp, KEY, "utf-8").toUpperCase();
				resultObj.put("sign", paySign);
				resultObj.put("appid", APP_ID);
				resultObj.put("signType", "MD5");
				resultObj.put("orderId", orderNum);
				resultObj.put("partnerid", MCH_ID);
				// 返回给小程序端需要的参数
				logger.debug(resultObj.toString());
			}
		} catch (Exception e) {
			logger.error("系统异常:" + e.getMessage());
		}
		return resultObj;
	}
	/**
	 * 查询支付结果/订单状态
	 * 
	 * @param orderNo
	 * @return
	 * @throws Exception
	 */
	public static Map<String, String> getPayResult(String orderNo) throws Exception {

		/**
		 * 生成微信「订单状态查询」请求数据
		 */
		Map<String, String> dataMap = new HashMap<>();
		dataMap.put("appid", APP_ID);
		dataMap.put("mch_id", MCH_ID);
		dataMap.put("out_trade_no", orderNo);
		dataMap.put("nonce_str", getUUID());
		/**
		 * 生成签名 MD5 编码
		 */
		String md5Sign = getMD5Sign(dataMap, KEY, FIELD_SIGN);
		dataMap.put("sign", md5Sign);

		/**
		 * 发送请求数据
		 */
		String resXml = requestWithoutCert(ORDER_QUERY, dataMap, 5000, 10000);
		/**
		 * 解析请求数据
		 */
		dataMap.clear();
		dataMap = processResponseXml(resXml);

		/**
		 * 返回处理结果
		 */
		return dataMap;
	}

	public static Map<String, String> getReceiveMap(HttpServletRequest request) {
		try {
			ServletInputStream inputStream = request.getInputStream();
			String stringFromStream = getStringFromStream(inputStream);
			Map<String, String> processResponseXml = processResponseXml(stringFromStream);
			return processResponseXml;
		} catch (Exception e) {
			throw new RuntimeException("处理微flowrecharge:" + e.getMessage());
		}

	}

	/**
	 * 处理 HTTPS API返回数据，转换成Map对象。return_code为SUCCESS时，验证签名。
	 *
	 */
	private static Map<String, String> processResponseXml(String xmlStr) throws Exception {
		String RETURN_CODE = "return_code";
		String return_code;
		Map<String, String> respData = xml2Map(xmlStr);
		if (respData.containsKey(RETURN_CODE)) {
			return_code = respData.get(RETURN_CODE);
		} else {
			throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
		}

		if (return_code.equals(FAIL)) {
			return respData;
		} else if (return_code.equals(SUCCESS)) {
			/**
			 * 签名校验
			 */
			if (signValidate(respData, KEY, FIELD_SIGN)) {
				return respData;
			} else {
				throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
			}
		} else {
			throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
		}
	}

	/**
	 * 生成jsapi微信支付「调起支付接口」请求参数
	 *
	 * @param data
	 *            源数据
	 * @param key
	 *            签名密钥
	 * @param fieldSign
	 *            签名字段名(固定值 sign)
	 * @return
	 * @throws Exception
	 */
	private static Map<String, String> getJsapiPayOrder(Map<String, String> data, String key, String fieldSign)	throws Exception {
		Map<String, String> resultMap = new HashMap<>();
		resultMap.put("appId", data.get("appid"));
		resultMap.put("package", "prepay_id="+data.get("prepay_id"));
		resultMap.put("nonceStr", data.get("nonce_str"));
		resultMap.put("timeStamp", getTimeStampSecond());
		resultMap.put("signType", "MD5");
		/**
		 * 生成签名
		 */
		String sign = getMD5Sign(resultMap, key, fieldSign);
		resultMap.put("paySign", sign);

		return resultMap;
	}


	/**
	 * 生成微信支付「调起支付接口」请求参数
	 *
	 * @param data
	 *            源数据
	 * @param key
	 *            签名密钥
	 * @param fieldSign
	 *            签名字段名(固定值 sign)
	 * @return
	 * @throws Exception
	 */
	private static Map<String, String> getPayOrder(Map<String, String> data, String key, String fieldSign)	throws Exception {
		Map<String, String> resultMap = new HashMap<>();
		resultMap.put("appid", data.get("appid"));
		resultMap.put("partnerid", data.get("mch_id"));
		resultMap.put("prepayid", data.get("prepay_id"));
		resultMap.put("package", "Sign=WXPay");
		resultMap.put("noncestr", data.get("nonce_str"));
		resultMap.put("timestamp", getTimeStampSecond());
		resultMap.put("mweburl", data.get("mweb_url"));
		resultMap.put("codeurl", data.get("code_url"));
		/**
		 * 生成签名
		 */
		String sign = getMD5Sign(resultMap, key, fieldSign);
		resultMap.put("sign", sign);

		return resultMap;
	}

	/**
	 * 获取支付订单号
	 *
	 * @param wxPayConfigure
	 *            微信支付配置信息
	 * @param orderNo
	 *            外部订单号
	 * @return 微信支付流水号
	 * @throws Exception
	 */
	public static String getPayNo(String orderNo) throws Exception {
		Map<String, String> resultMap = getPayResult(orderNo);
		if (resultMap.isEmpty()) {
			return null;
		}
		if (SUCCESS.equalsIgnoreCase(resultMap.get("trade_state"))) {
			return resultMap.get("transaction_id");
		}
		return null;
	}

	/**
	 * 生成UUID字符串
	 * 
	 * @return string UUID
	 */
	private static String getUUID() {
		return UUID.randomUUID().toString().replaceAll("-", "");
	}

	/**
	 * 生成 md5 签名.将 map 数据按照 key 的升序进行排列 使用URL键值对的格式(即key1=value1&key2=value2…)
	 * 传送的sign参数不参与签名
	 *
	 * @param data
	 *            待签名数据
	 * @param key
	 *            密钥
	 * @param fieldSign
	 *            签名字段(sign)
	 * @return 签名
	 */
	private static String getMD5Sign(final Map<String, String> data, String key, String fieldSign) {
		Set<String> keySet = data.keySet();
		String[] keyArray = keySet.toArray(new String[keySet.size()]);
		Arrays.sort(keyArray);
		StringBuilder sb = new StringBuilder();
		for (String k : keyArray) {
			if (k.equals(fieldSign)) {
				continue;
			}
			if (data.get(k) != null && data.get(k).trim().length() > 0) // 参数值为空，则不参与签名
				sb.append(k).append("=").append(data.get(k).trim()).append("&");
		}
		sb.append("key=").append(key);
		return md5(sb.toString()).toUpperCase();
	}

	/**
	 * 校验签名
	 *
	 * @param data
	 *            待校验数据
	 * @param key
	 *            密钥
	 * @param fieldSign
	 *            签名字段(sign)
	 * @return
	 */
	private static boolean signValidate(Map<String, String> data, String key, String fieldSign) {
		if (!data.containsKey(fieldSign)) {
			return false;
		}
		String sign = data.get(fieldSign);
		return getMD5Sign(data, key, fieldSign).equalsIgnoreCase(sign);
	}

	/**
	 * 普通 md5 编码
	 * 
	 * @param url
	 * @return
	 */
	private static String md5(String url) {
		try {
			MessageDigest md5 = MessageDigest.getInstance("MD5");
			md5.update(url.getBytes("UTF-8"));
			byte messageDigest[] = md5.digest();

			StringBuffer hexString = new StringBuffer();
			for (int i = 0; i < messageDigest.length; i++) {
				String t = Integer.toHexString(0xFF & messageDigest[i]);
				if (t.length() == 1) {
					hexString.append("0" + t);
				} else {
					hexString.append(t);
				}
			}
			return hexString.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 从输入流中读取字符数据
	 *
	 * @param inputStream
	 *            servlet request inputStream
	 * @return
	 * @throws IOException
	 */
	private static String getStringFromStream(InputStream inputStream) throws IOException {
		if (inputStream == null) {
			return null;
		}
		StringBuffer sb = new StringBuffer();
		String s;
		BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
		while ((s = in.readLine()) != null) {
			sb.append(s);
		}
		in.close();
		inputStream.close();

		return sb.toString();
	}

	/**
	 * 将 xml 转换为 map
	 *
	 * @param xmlStr
	 *            xml 格式字符串
	 * @return map 对象
	 * @throws DocumentException
	 */
	private static Map<String, String> xml2Map(String xmlStr) throws DocumentException {
		Map<String, String> map = new HashMap<String, String>();
		Document doc = DocumentHelper.parseText(xmlStr);

		if (doc == null)
			return map;
		Element root = doc.getRootElement();
		for (Iterator iterator = root.elementIterator(); iterator.hasNext();) {
			Element e = (Element) iterator.next();
			map.put(e.getName(), e.getText());
		}
		return map;
	}

	/**
	 * map 转 xml (仅微信支付xml格式)
	 *
	 * @param dataMap
	 *            map 数据
	 * @return
	 */
	private static String map2Xml(Map<String, String> dataMap) {
		StringBuffer sb = new StringBuffer();
		sb.append("<xml>");
		for (String key : dataMap.keySet()) {
			String value = "<![CDATA[" + dataMap.get(key) + "]]>";
			sb.append("<" + key + ">" + value + "</" + key + ">");
		}
		sb.append("</xml>");

		return sb.toString();
	}

	/**
	 * 判断 map 对象是否为空 当 map != null 且 map 中有值时,返回 true,否则返回 false
	 *
	 * @param map
	 *            map 对象
	 * @return map 对象为空的判断结果
	 */
	private static boolean isEmpty(Map map) {
		if (map == null || map.isEmpty()) {
			return true;
		}
		return false;
	}

	/**
	 * 获取当前时间戳(秒级,10位数字)
	 *
	 * @return 时间戳
	 */
	private static String getTimeStampSecond() {
		return Math.round(new Date().getTime() / 1000) + "";
	}

	/**
	 * 不需要证书的请求
	 * 
	 * @param strUrl
	 *            String
	 * @param reqData
	 *            向wxpay post的请求数据
	 * @param connectTimeoutMs
	 *            超时时间，单位是毫秒
	 * @param readTimeoutMs
	 *            超时时间，单位是毫秒
	 * @return API返回数据
	 * @throws Exception
	 */
	private static String requestWithoutCert(String strUrl, Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
		String UTF8 = "UTF-8";
		String reqBody = map2Xml(reqData);
		URL httpUrl = new URL(strUrl);
		HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
		httpURLConnection.setDoOutput(true);
		httpURLConnection.setRequestMethod("POST");
		httpURLConnection.setConnectTimeout(connectTimeoutMs);
		httpURLConnection.setReadTimeout(readTimeoutMs);
		httpURLConnection.connect();
		OutputStream outputStream = httpURLConnection.getOutputStream();
		outputStream.write(reqBody.getBytes(UTF8));

		// 获取内容
		InputStream inputStream = httpURLConnection.getInputStream();
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
		final StringBuffer stringBuffer = new StringBuffer();
		String line = null;
		while ((line = bufferedReader.readLine()) != null) {
			stringBuffer.append(line).append("\n");
		}
		String resp = stringBuffer.toString();
		if (stringBuffer != null) {
			try {
				bufferedReader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if (inputStream != null) {
			try {
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if (outputStream != null) {
			try {
				outputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return resp;
	}

	/**
	 * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
	 * 
	 * @param params
	 *            需要排序并参与字符拼接的参数组
	 * @return 拼接后字符串
	 */
	private static String createLinkString(Map<String, String> params) {
		List<String> keys = new ArrayList<String>(params.keySet());
		Collections.sort(keys);
		String prestr = "";
		for (int i = 0; i < keys.size(); i++) {
			String key = keys.get(i);
			String value = params.get(key);
			if (i == keys.size() - 1) {// 拼接时，不包括最后一个&字符
				prestr = prestr + key + "=" + value;
			} else {
				prestr = prestr + key + "=" + value + "&";
			}
		}
		return prestr;
	}

	/**
	 * 签名字符串
	 * 
	 * @param text需要签名的字符串
	 * @param key
	 *            密钥
	 * @param input_charset编码格式
	 * @return 签名结果
	 */
	private static String sign(String text, String key, String input_charset) {
		text = text + "&key=" + key;
		return DigestUtils.md5Hex(getContentBytes(text, input_charset));
	}

	/**
	 * @param content
	 * @param charset
	 * @return
	 * @throws SignatureException
	 * @throws UnsupportedEncodingException
	 */
	private static byte[] getContentBytes(String content, String charset) {
		if (charset == null || "".equals(charset)) {
			return content.getBytes();
		}
		try {
			return content.getBytes(charset);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
		}
	}

	/**
	 * 
	 * @param requestUrl请求地址
	 * @param requestMethod请求方法
	 * @param outputStr参数
	 */
	private static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
		// 创建SSLContext
		StringBuffer buffer = null;
		try {
			URL url = new URL(requestUrl);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setRequestMethod(requestMethod);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.connect();
			// 往服务器端写内容
			if (null != outputStr) {
				OutputStream os = conn.getOutputStream();
				os.write(outputStr.getBytes("utf-8"));
				os.close();
			}
			// 读取服务器端返回的内容
			InputStream is = conn.getInputStream();
			InputStreamReader isr = new InputStreamReader(is, "utf-8");
			BufferedReader br = new BufferedReader(isr);
			buffer = new StringBuffer();
			String line = null;
			while ((line = br.readLine()) != null) {
				buffer.append(line);
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return buffer.toString();
	}

	/**
	 * 解析xml,返回第一级元素键值对。如果第一级元素有子节点，则此节点的值是子节点的xml数据。
	 * 
	 * @param strxml
	 * @return
	 * @throws JDOMException
	 * @throws IOException
	 */
	private static Map<String, String> doXMLParse(String strxml) throws Exception {
		if (null == strxml || "".equals(strxml)) {
			return null;
		}

		Map<String, String> m = new HashMap<String, String>();
		InputStream in = String2Inputstream(strxml);
		SAXReader builder = new SAXReader();
		builder.setEncoding("UTF-8");
		Document doc = builder.read(in);
		Element root = doc.getRootElement();
		List list = root.elements();
		// List list = root.getChildren();
		Iterator it = list.iterator();
		while (it.hasNext()) {
			Element e = (Element) it.next();
			String k = e.getName();
			String v = "";
			// List children = e.getChildren();
			List children = e.elements();
			if (children.isEmpty()) {
				// v = e.getTextNormalize();
				v = e.getTextTrim();
			} else {
				v = getChildrenText(children);
			}

			m.put(k, v);
		}

		// 关闭流
		in.close();

		return m;
	}

	/**
	 * 获取子结点的xml
	 * 
	 * @param children
	 * @return String
	 */
	private static String getChildrenText(List children) {
		StringBuffer sb = new StringBuffer();
		if (!children.isEmpty()) {
			Iterator it = children.iterator();
			while (it.hasNext()) {
				Element e = (Element) it.next();
				String name = e.getName();
				// String value = e.getTextNormalize();
				String value = e.getTextTrim();
				// List list = e.getChildren();
				List list = e.elements();
				sb.append("<" + name + ">");
				if (!list.isEmpty()) {
					sb.append(getChildrenText(list));
				}
				sb.append(value);
				sb.append("</" + name + ">");
			}
		}

		return sb.toString();
	}

	private static InputStream String2Inputstream(String str) throws IOException {
		return IOUtils.toInputStream(str, StandardCharsets.UTF_8.name());
	}
}
